/**
 * 
 */
package org.gaming.common.apidoc.service;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.gaming.common.apidoc.annotation.ApiDocument;
import org.gaming.common.apidoc.model.AbstractApiDoc;
import org.gaming.db.repository.BaseRepository;
import org.gaming.db.usecase.SlimDao;
import org.gaming.ruler.lifecycle.Lifecycle;
import org.gaming.ruler.spring.Spring;
import org.gaming.tool.GsonUtil;
import org.gaming.tool.ListMapUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author YY
 *
 */
public abstract class AbstractApiDocService<T extends AbstractApiDoc> implements Lifecycle {
	
	protected static Logger logger = LoggerFactory.getLogger(AbstractApiDocService.class);
	
	private Class<T> clazz;
	
	@SuppressWarnings("unchecked")
	public AbstractApiDocService() {
		this.clazz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}
	
	public List<T> getAll() {
		return this.repository().getAllInDb();
	}
	
	private BaseRepository<T> repository;
	protected final BaseRepository<T> repository() {
		if(repository == null) {
			repository = SlimDao.getRepository(clazz);
		}
		return repository;
	}
	
	public abstract T newApiDoc();
	
	
	@Override
	public void start() throws Exception {
		List<T> list = this.getAll();
		Map<String, T> docMap = ListMapUtil.listToMap(list, AbstractApiDoc::getClassMethod);
		
		Collection<Object> beans1 = Spring.getBeansWithAnnotation(Controller.class);
		Collection<Object> beans2 = Spring.getBeansWithAnnotation(RestController.class);

		Set<Object> beans = new HashSet<>();
		beans.addAll(beans1);
		beans.addAll(beans2);

		List<T> insertList = new ArrayList<>();
		List<T> updateList = new ArrayList<>();
		for (Object bean : beans) {
			for(Method method : bean.getClass().getDeclaredMethods()) {
				ApiDocument apiDocument = method.getAnnotation(ApiDocument.class);
				RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
				if(apiDocument == null || requestMapping == null) {
					continue;
				}
				String classMethod = bean.getClass().getSimpleName() + "." + method.getName();
				T apiDoc = docMap.get(classMethod);
				if(apiDoc == null) {
					apiDoc = newApiDoc();
					apiDoc.setClassMethod(classMethod);
					isApiChange(apiDoc, apiDocument, method, requestMapping.value()[0]);
					insertList.add(apiDoc);
				} else {
					if(isApiChange(apiDoc, apiDocument, method, requestMapping.value()[0])) {
						updateList.add(apiDoc);
					}
				}
			}
		}
		this.repository().insertAll(insertList);
		this.repository().updateAll(updateList);
	}
	
	private boolean isApiChange(T apiDoc, ApiDocument apiDocument, Method method, String hrefUrl) {
		boolean change = false;
		if(!hrefUrl.equals(apiDoc.getHrefUrl())) {
			apiDoc.setHrefUrl(hrefUrl);
			change = true;
		}
		if(!apiDocument.value().equals(apiDoc.getComment())) {
			apiDoc.setComment(apiDocument.value());
			change = true;
		}
		StringBuilder builder = new StringBuilder();
		for(Parameter parameter : method.getParameters()) {
			if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
				continue;
			}
			if (parameter.getType() == int.class || parameter.getType() == Integer.class
					|| parameter.getType() == long.class || parameter.getType() == Long.class
					|| parameter.getType() == String.class) {
				RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
				if(requestParam != null) {
					builder.append(requestParam.value() + ":" + parameter.getType().getSimpleName()).append(";\n");
				} else {
					builder.append(parameter.getName() + ":" + parameter.getType().getSimpleName()).append(";\n");
				}
			} else if(parameter.getType() == java.util.List.class) {
				ParameterizedType typeImpl = (ParameterizedType)parameter.getParameterizedType();
				RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
				if (requestParam != null) {
					if (typeImpl.getActualTypeArguments()[0] == Long.class) {
						builder.append(requestParam.value() + ":" + "long[]").append(";\n");
					} else if (typeImpl.getActualTypeArguments()[0] == Integer.class) {
						builder.append(requestParam.value() + ":" + "int[]").append(";\n");
					} else if (typeImpl.getActualTypeArguments()[0] == String.class) {
						builder.append(requestParam.value() + ":" + "String[]").append(";\n");
					} else {
						builder.append(
								requestParam.value() + ":" + typeImpl.getActualTypeArguments()[0].getTypeName() + "[]")
								.append(";\n");
					}
				} else {
					builder.append(
							parameter.getName() + ":" + typeImpl.getActualTypeArguments()[0].getTypeName() + "[]")
							.append(";\n");
				}
			} else {
				try {
					Class<?> clazz = parameter.getType();
					Object requestObj = clazz.newInstance();
					
					for(Field field : clazz.getDeclaredFields()) {
						if(List.class.isAssignableFrom(field.getType())) {
							List<Object> list = new ArrayList<>();
							Class<?> pClazz = (Class<?>)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
							if(int.class == pClazz || Integer.class == pClazz) {
								list.add(0);
							} else {
								list.add(pClazz.newInstance());
							}
							field.setAccessible(true);
							field.set(requestObj, list);
						}
					}
					builder.append(GsonUtil.toJson(requestObj));
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		if(!builder.toString().equals(apiDoc.getRequest())) {
			apiDoc.setRequest(builder.toString());
			change = true;
		}
		try {
			String response = "";
			if(method.getReturnType() == void.class) {
				
			} else if(List.class.isAssignableFrom(method.getReturnType())) {
				List<Object> returnList = new ArrayList<>();
				Class<?> clazz = (Class<?>)((ParameterizedType)method.getGenericReturnType()).getActualTypeArguments()[0];
				returnList.add(clazz.newInstance());
				response = GsonUtil.toJson(returnList);
			} else {
				Object returnObj = method.getReturnType().newInstance();
				if(method.getGenericReturnType() instanceof ParameterizedType) {
					Class<?> clazz = (Class<?>)((ParameterizedType)method.getGenericReturnType()).getActualTypeArguments()[0];
					
					for(Field field : method.getReturnType().getDeclaredFields()) {
						if(List.class.isAssignableFrom(field.getType())) {
							List<Object> list = new ArrayList<>();
							list.add(clazz.newInstance());
							field.setAccessible(true);
							field.set(returnObj, list);
						}
					}
				} else {
					for(Field field : method.getReturnType().getDeclaredFields()) {
						if(List.class.isAssignableFrom(field.getType())) {
							List<Object> list = new ArrayList<>();
							Class<?> pClazz = (Class<?>)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
							if(int.class == pClazz || Integer.class == pClazz) {
								list.add(0);
							} else {
								list.add(pClazz.newInstance());
							}
							field.setAccessible(true);
							field.set(returnObj, list);
						}
					}
				}
				response = GsonUtil.toJson(returnObj);
			}
			if(!response.equals(apiDoc.getResponse())) {
				apiDoc.setResponse(response);
				change = true;
			}
		} catch (Exception e) {
			logger.error(apiDocument.value() + " " + method.getName() + "异常", e);
		}
		return change;
	}
}
